Upgrade dependencies, update tools with new API features#21
Upgrade dependencies, update tools with new API features#21
Conversation
1. Upgrade all dependencies to latest versions 2. Use fetch instead of axios 3. Use registerTool with input/output zod schemas 4. Update with new API features
There was a problem hiding this comment.
Code Review
This pull request refactors the MCP server to use a centralized apiQuery utility based on the native fetch API, replacing axios. It introduces a comprehensive set of Zod schemas for request/response validation and reconfigures the TypeScript environment to use NodeNext. While the refactoring improves type safety, several critical issues were identified: multiple dependency versions in package.json and the Biome schema appear to be hallucinated and do not exist. Additionally, the code uses invalid Zod methods like z.url() instead of z.string().url(), and recursive schemas are not properly wrapped in z.lazy(). There is also a significant concern regarding the use of server.registerTool, as the standard MCP SDK uses server.tool() and does not support the provided metadata structure, which will lead to runtime failures.
Code Review — PR #21: Upgrade dependencies, update tools with new API featuresOverall this is a well-structured modernisation PR. The centralised Overview
🔴 Critical1. Entire test suite deleted with no replacement
🟠 Bugs / Correctness2. Schema-validation In const parsed = opts.schema.safeParse(raw)
if (!parsed.success) {
throw new ApiError(`Response validation failed: ${parsed.error.message}`, res.status, raw)
}This path is only reached when 3. const sharedPreconditions = await apiQuery(
`/api/public/v0/project/${projectCode}/shared-precondition`,
{
schema: listSharedPreconditionsOutputSchema.shape.sharedPreconditions, // ← array schema
...
}
)
const result = { sharedPreconditions }The raw API returns an array, but the output schema wraps it in 4. All other tools explicitly re-throw user-friendly messages for 401/403. } catch (error: unknown) {
if (error instanceof ApiError) {
if (error.status === 404) {
throw new Error(`Project with code '${projectCode}' not found.`)
}
throw new Error(`Failed to fetch test case folders: ${error.message}`) // 401/403 land here
}An auth failure gives 🟡 Design / Maintainability5. Duplicated error-handling pattern across every tool Every tool file implements the same function rethrowApiError(error: unknown, context: string, projectCode?: string): never {
if (error instanceof ApiError) {
if (error.status === 401) throw new Error('Invalid or missing API key')
if (error.status === 403) throw new Error('Insufficient permissions or suspended tenant')
if (error.status === 404 && projectCode)
throw new Error(`Project with code '${projectCode}' not found.`)
throw new Error(`${context}: ${error.message}`)
}
throw error
}6. export const testStepSchemaShape = {
// ...
get subSteps() {
return z.array(testStepSchema).optional().describe('...')
},
}The getter is needed to break the circular reference, but it constructs a new schema instance every time const testStepSchema: z.ZodType<TestStep> = z.object({
...
subSteps: z.lazy(() => z.array(testStepSchema)).optional(),
})7. const headers: Record<string, string> = {
Authorization: `ApiKey ${QASPHERE_API_KEY}`,
'Content-Type': 'application/json', // unnecessary for GET
}Harmless but semantically incorrect. Consider only setting it when 🟢 Positive observations
SummaryThe architecture is solid. The three items that should be addressed before merging:
The duplicated error handler and the |
| "dev": "tsx src/index.ts", | ||
| "lint": "biome lint --write .", | ||
| "format": "biome format --write .", | ||
| "check": "biome check src", |
There was a problem hiding this comment.
check does lint+format in the same command
| "lint-staged": "^15.5.1", | ||
| "tsx": "^4.19.3", | ||
| "typescript": "^5.8.2", | ||
| "vitest": "^3.1.1" |
There was a problem hiding this comment.
There are no tests, so removed vitest
| type: z | ||
| .string() | ||
| .describe('Type of the step. Known values: "standalone" | "shared" | "shared_sub_step"'), |
There was a problem hiding this comment.
Intentionally didn't keep any enums in output schemas, so things don't break if we add new values in the backend
| "typescript": "^5.8.2", | ||
| "vitest": "^3.1.1" | ||
| "lint-staged": "^16.4.0", | ||
| "tsx": "^4.21.0", |
There was a problem hiding this comment.
node can run typescript natively now, this may not be necessary anymore
| "dotenv": "^16.4.5", | ||
| "zod": "^3.22.4" | ||
| "@modelcontextprotocol/sdk": "^1.29.0", | ||
| "dotenv": "^17.4.2", |
| "target": "ES2020", | ||
| "module": "ES2020", | ||
| "moduleResolution": "node", | ||
| "target": "ES2022", | ||
| "module": "NodeNext", |
There was a problem hiding this comment.
typescript 6 defaults to a roaming (always latest) target and module versions, consider not pinning them here
| "moduleResolution": "NodeNext", | ||
| "outDir": "./dist", | ||
| "rootDir": "./src", | ||
| "strict": true, |
There are some minor non-backward compatible changes in this PR.